package com.tang.sharding.core;

import com.tang.sharding.ShardProperties;
import com.tang.sharding.enums.ShardingCycle;
import com.tang.sharding.schedule.task.jdbc.CreateTableRunnableTask;
import com.tang.sharding.schedule.task.jdbc.InitializingTableRunnableTask;
import com.tang.sharding.schedule.task.jdbc.JdbcManager;
import com.tang.sharding.schedule.task.manager.AbstractTaskManager;
import io.shardingsphere.api.config.rule.ShardingRuleConfiguration;
import io.shardingsphere.api.config.rule.TableRuleConfiguration;
import io.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.config.CronTask;

import javax.sql.DataSource;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author Tang
 * @classname ShardDataSourceFactory
 * @description [ 数据源 ]
 * @date 2021/7/31 14:33
 */
@Slf4j
public class ShardDataSourceFactory {

    private final ShardProperties shardProperties;

    protected BlockingQueue<Object> taskBlockingQueue;

    private static ThreadPoolExecutor poolExecutor;

    @Setter
    private JdbcManager jdbcManager;

    @Setter
    private AbstractTaskManager taskManager;

    static {
        int core = Runtime.getRuntime().availableProcessors();
        poolExecutor = new ThreadPoolExecutor(core, core + 1, 100, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory());
    }

    public ShardDataSourceFactory(ShardProperties shardProperties) {
        this.shardProperties = shardProperties;
        this.taskBlockingQueue = AbstractTaskManager.getTaskBlockingQueue();
    }

    /**
     * 创建数据源
     *
     * @param properties
     * @param dataSourceMap
     * @return
     * @throws Exception
     */
    public DataSource createDataSource(Properties properties, Map<String, DataSource> dataSourceMap) throws Exception {
        ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();
        shardingRuleConfiguration.setDefaultDataSourceName(shardProperties.getDataSourceName());
        Collection<TableRuleConfiguration> tableRuleConfigs = shardingRuleConfiguration.getTableRuleConfigs();
        buildRuleConfigs(tableRuleConfigs);
        return ShardingDataSourceFactory.createDataSource(dataSourceMap,
                shardingRuleConfiguration, new HashMap<>(8), properties);
    }

    /**
     * 规则构建
     *
     * @param tableRuleConfigs
     */
    private void buildRuleConfigs(Collection<TableRuleConfiguration> tableRuleConfigs) {
        shardProperties.getRuleList().forEach(tableRule -> {
            tableRuleConfigs.add(
                    new ShardTableRuleConfig(tableRule).buildTableRule(shardProperties.getDataSourceName())
            );
            runTask(tableRule);
        });
    }

    /**
     * run task
     *
     * @param tableRule
     */
    private void runTask(ShardProperties.TableRule tableRule) {
        ShardingCycle shardingCycle = tableRule.getShardingCycle();
        ShardProperties.CustomCycle customCycle = tableRule.getCustomCycle();
        try {
            String taskId = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
            // 启动时表检测任务
            poolExecutor.execute(new InitializingTableRunnableTask(
                    taskId, jdbcManager, tableRule));
            // 构建定时任务
            taskBlockingQueue.put(
                    new CronTask(
                            new CreateTableRunnableTask(
                                    taskId, jdbcManager, tableRule
                            ),
                            shardingCycle == ShardingCycle.NON ? customCycle.getCronExpression() : shardingCycle.getCron()
                    )
            );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}